import * as cc from "cc";
const { ccclass, property } = cc._decorator;

@ccclass
export default class MyAtlasSprite extends cc.Sprite {
    constructor() {
        super();
        this.createAssembler();
    }

    protected createAssembler() {
        if (!this._assembler) {
            this._assembler = new MyAtlasAssembler();
            this._assembler.init(this);
        }
    }

    public setAtlasData(data: { spriteFrame: cc.SpriteFrame, texture: cc.Texture2D, uv, width: number, height: number }) {
        this.trim = false;
        this.sizeMode = cc.Sprite.SizeMode.CUSTOM;
        let trans = this.node.getComponent(cc.UITransform);
        if (undefined !== data.width) {
            trans.width = data.width;
        } else {

            trans.width = data.uv.width;
        }
        if (undefined !== data.height) {
            trans.height = data.height;
        } else {
            trans.height = data.uv.height;
        }
        this.spriteFrame = data.uv.originalSpriteFrame;
        // this.spriteFrame = data.spriteFrame;
        // this._assembler.setAtlasUV(data.uv);
    }

    public setUV(left: number, right: number, bottom: number, top: number) {
        this.createAssembler();
        this._assembler.setUV(left, right, bottom, top);
    }
}

class RenderData {
    vDatas = [];
    uintVDatas = [];
    iDatas = [];

    updateMesh(index, vertices, indices) {
        this.vDatas[index] = vertices;
        this.uintVDatas[index] = new Uint32Array(vertices.buffer, 0, vertices.length);
        this.iDatas[index] = indices;
    }

    createData(index, verticesFloats, indicesCount) {
        let vertices = new Float32Array(verticesFloats);
        let indices = new Uint16Array(indicesCount);
        this.updateMesh(index, vertices, indices);
    }

    createQuadData(index, verticesFloats, indicesCount) {
        this.createData(index, verticesFloats, indicesCount);
        this.initQuadIndices(this.iDatas[index]);
    }

    initQuadIndices(indices) {
        let count = indices.length / 6;
        for (let i = 0, idx = 0; i < count; i++) {
            let vertextID = i * 4;
            indices[idx++] = vertextID;
            indices[idx++] = vertextID + 1;
            indices[idx++] = vertextID + 2;
            indices[idx++] = vertextID + 1;
            indices[idx++] = vertextID + 3;
            indices[idx++] = vertextID + 2;
        }
    }

}

export class MyAtlasAssembler {
    protected _renderData: RenderData;
    floatsPerVert = 5;

    verticesCount = 4;
    indicesCount = 6;

    uvOffset = 2;
    colorOffset = 4;
    _local;

    constructor() {
        this._renderData = new RenderData();

        this.initData();
        this.initLocal();
    }

    get verticesFloats() {
        return this.verticesCount * this.floatsPerVert;
    }

    initData() {
        let data = this._renderData;
        data.createQuadData(0, this.verticesFloats, this.indicesCount);
    }
    initLocal() {
        this._local = [0, 0, 0, 0];
    }

    updateColor(comp, color) {
        let uintVerts = this._renderData.uintVDatas[0];
        if (!uintVerts) return;
        color = color != null ? color : comp.node.color._val;
        let floatsPerVert = this.floatsPerVert;
        let colorOffset = this.colorOffset;
        for (let i = colorOffset, l = uintVerts.length; i < l; i += floatsPerVert) {
            uintVerts[i] = color;
        }
    }

    // getBuffer(renderer) {
    //     return cc.renderer._handle._meshBuffer;
    // }

    // updateWorldVerts(comp) {
    //     let local = this._local;
    //     let verts = this._renderData.vDatas[0];

    //     let matrix = this._renderComp.node._worldMatrix;
    //     let matrixm = matrix.m,
    //         a = matrixm[0], b = matrixm[1], c = matrixm[4], d = matrixm[5],
    //         tx = matrixm[12], ty = matrixm[13];

    //     let vl = local[0], vr = local[2],
    //         vb = local[1], vt = local[3];

    //     let justTranslate = a === 1 && b === 0 && c === 0 && d === 1;

    //     if (justTranslate) {
    //         // left bottom
    //         verts[0] = vl + tx;
    //         verts[1] = vb + ty;
    //         // right bottom
    //         verts[5] = vr + tx;
    //         verts[6] = vb + ty;
    //         // left top
    //         verts[10] = vl + tx;
    //         verts[11] = vt + ty;
    //         // right top
    //         verts[15] = vr + tx;
    //         verts[16] = vt + ty;
    //     } else {
    //         let al = a * vl, ar = a * vr,
    //             bl = b * vl, br = b * vr,
    //             cb = c * vb, ct = c * vt,
    //             db = d * vb, dt = d * vt;

    //         // left bottom
    //         verts[0] = al + cb + tx;
    //         verts[1] = bl + db + ty;
    //         // right bottom
    //         verts[5] = ar + cb + tx;
    //         verts[6] = br + db + ty;
    //         // left top
    //         verts[10] = al + ct + tx;
    //         verts[11] = bl + dt + ty;
    //         // right top
    //         verts[15] = ar + ct + tx;
    //         verts[16] = br + dt + ty;
    //     }
    // }

    fillBuffers2(comp, renderer) {
        // if (renderer.worldMatDirty) {
        //     this.updateWorldVerts(comp);
        // }

        // let renderData = this._renderData;
        // let vData = renderData.vDatas[0];
        // let iData = renderData.iDatas[0];

        // let buffer = this.getBuffer(renderer);
        // let offsetInfo = buffer.request(this.verticesCount, this.indicesCount);

        // // buffer data may be realloc, need get reference after request.

        // // fill vertices
        // let vertexOffset = offsetInfo.byteOffset >> 2,
        //     vbuf = buffer._vData;

        // if (vData.length + vertexOffset > vbuf.length) {
        //     vbuf.set(vData.subarray(0, vbuf.length - vertexOffset), vertexOffset);
        // } else {
        //     vbuf.set(vData, vertexOffset);
        // }

        // // fill indices
        // let ibuf = buffer._iData,
        //     indiceOffset = offsetInfo.indiceOffset,
        //     vertexId = offsetInfo.vertexOffset;
        // for (let i = 0, l = iData.length; i < l; i++) {
        //     ibuf[indiceOffset++] = vertexId + iData[i];
        // }
    }

    fillBuffers (sprite, renderer) {
        if (sprite === null) {
            return;
        }

        // const buffer: MeshBuffer = renderer.createBuffer(
        //     sprite.renderData!.vertexCount,
        //     sprite.renderData!.indicesCount,
        // );
        // const commitBuffer: IUIRenderData = renderer.createUIRenderData();
        const dataList = sprite.renderData!.data;
        const node = sprite.node;

        let buffer = renderer.acquireBufferBatch()!;
        let vertexOffset = buffer.byteOffset >> 2;
        let indicesOffset = buffer.indicesOffset;
        let vertexId = buffer.vertexOffset;

        const isRecreate = buffer.request();
        if (!isRecreate) {
            buffer = renderer.currBufferBatch!;
            vertexOffset = 0;
            indicesOffset = 0;
            vertexId = 0;
        }

        // buffer data may be reallocated, need get reference after request.
        const vBuf = buffer.vData!;
        const iBuf = buffer.iData!;
        const vData = sprite.renderData!.vData!;
        const data0 = dataList[0];
        const data3 = dataList[3];
        const matrix = node.worldMatrix;
        const a = matrix.m00; const b = matrix.m01;
        const c = matrix.m04; const d = matrix.m05;
        const tx = matrix.m12; const ty = matrix.m13;
        const vl = data0.x; const vr = data3.x;
        const vb = data0.y; const vt = data3.y;
        const al = a * vl; const ar = a * vr;
        const bl = b * vl; const br = b * vr;
        const cb = c * vb; const ct = c * vt;
        const db = d * vb; const dt = d * vt;
        // left bottom
        vData[0] = al + cb + tx;
        vData[1] = bl + db + ty;
        // right bottom
        vData[9] = ar + cb + tx;
        vData[10] = br + db + ty;
        // left top
        vData[18] = al + ct + tx;
        vData[19] = bl + dt + ty;
        // right top
        vData[27] = ar + ct + tx;
        vData[28] = br + dt + ty;

        vBuf.set(vData, vertexOffset);

        // fill index data
        iBuf[indicesOffset++] = vertexId;
        iBuf[indicesOffset++] = vertexId + 1;
        iBuf[indicesOffset++] = vertexId + 2;
        iBuf[indicesOffset++] = vertexId + 2;
        iBuf[indicesOffset++] = vertexId + 1;
        iBuf[indicesOffset++] = vertexId + 3;
    }

    updateRenderData(sprite) {
        this.packToDynamicAtlas(sprite, sprite._spriteFrame);

        if (sprite._vertsDirty) {
            this.updateUVs(sprite);
            this.updateVerts(sprite);
            sprite._vertsDirty = false;
        }
    }

    updateUVs(sprite) {
        // let uv = sprite._spriteFrame.uv;
        // let uvOffset = this.uvOffset;
        // let floatsPerVert = this.floatsPerVert;
        // let verts = this._renderData.vDatas[0];
        // for (let i = 0; i < 4; i++) {
        //     let srcOffset = i * 2;
        //     let dstOffset = floatsPerVert * i + uvOffset;
        //     verts[dstOffset] = uv[srcOffset];
        //     verts[dstOffset + 1] = uv[srcOffset + 1];
        // }

        let size = sprite._spriteFrame.getOriginalSize();
        let w = sprite.node.width;
        let h = sprite.node.height;
        let r1 = size.width / size.height;
        let r2 = w / h;
        let uv = sprite._spriteFrame.uv;
        let x1 = uv[0];//左
        let x2 = uv[2];//右
        let y1 = uv[1];//下
        let y2 = uv[5];//上

        if (r2 > r1) {
            //按宽度适配
            let y0 = (y1 + y2) * 0.5;
            let ry = size.width * h / size.height / w;
            let o = (y2 - y1) * 0.5 * ry;
            y1 = y0 - o;
            y2 = y0 + o;
        } else if (r2 < r1) {
            //按高度适配
            let x0 = (x1 + x2) * 0.5;
            let rx = size.height * w / size.width / h;
            let o = (x2 - x1) * 0.5 * rx;
            x1 = x0 - o;
            x2 = x0 + o;
        }

        let vData = this._renderData.vDatas[0];
        let offset = 2;
        let floatsPerVert = 5;
        vData[offset] = x1;
        vData[offset + 1] = y1;

        offset += floatsPerVert;
        vData[offset] = x2;
        vData[offset + 1] = y1;

        offset += floatsPerVert;
        vData[offset] = x1;
        vData[offset + 1] = y2;

        offset += floatsPerVert;
        vData[offset] = x2;
        vData[offset + 1] = y2;
    }

    updateVerts(sprite) {
        let node = sprite.node,
            cw = node.width, ch = node.height,
            appx = node.anchorX * cw, appy = node.anchorY * ch,
            l, b, r, t;

        l = -appx;
        b = -appy;
        r = cw - appx;
        t = ch - appy;

        let local = this._local;
        local[0] = l;
        local[1] = b;
        local[2] = r;
        local[3] = t;
        // this.updateWorldVerts(sprite);
    }

    public setUV(left: number, right: number, bottom: number, top: number) {
        let vData = this._renderData.vDatas[0];
        let floatsPerVert = this.floatsPerVert;
        let offset = this.uvOffset;
        vData[offset] = left;
        vData[offset + 1] = bottom;

        offset += floatsPerVert;
        vData[offset] = right;
        vData[offset + 1] = bottom;

        offset += floatsPerVert;
        vData[offset] = left;
        vData[offset + 1] = top;

        offset += floatsPerVert;
        vData[offset] = right;
        vData[offset + 1] = top;

    }


    packToDynamicAtlas(comp, frame) {

        if (!frame._original && cc.dynamicAtlasManager && frame._texture.packable) {
            let packedFrame = cc.dynamicAtlasManager.insertSpriteFrame(frame);
            if (packedFrame) {
                frame._setDynamicAtlasFrame(packedFrame);
            }
        }
        let material = comp._materials[0];
        if (!material) return;

        if (material.getProperty('texture') !== frame._texture) {
            // texture was packed to dynamic atlas, should update uvs
            comp._vertsDirty = true;
            comp._updateMaterial();
        }
    }


}
const MyAtlasAssemblerMng = {
    getAssembler(spriteComp) {
        return MyAtlasAssembler;
    }
}
MyAtlasSprite.Assembler = MyAtlasAssemblerMng;